package my.webservices.security.handlers;

import java.io.IOException;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;

import my.webservices.security.EncryptUtil;

import org.apache.ws.security.WSConstants;
import org.apache.ws.security.WSPasswordCallback;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.support.PropertiesLoaderUtils;

public class WSSecurityHandler implements SOAPHandler<SOAPMessageContext>,
		CallbackHandler {

	Properties prop;

	private final static Logger LOGGER = LoggerFactory
			.getLogger(WSSecurityHandler.class);

	/**
	 * Constructor initializes properties
	 * */
	public WSSecurityHandler() throws Exception {

		prop = PropertiesLoaderUtils.loadAllProperties("wss4j.properties");
		LOGGER.debug("Properties after load {}", prop);
	}

	/**
	 * Returns special headers.
	 */
	public Set<QName> getHeaders() {

		Set<QName> HEADERS = new HashSet<QName>();

		HEADERS.add(new QName(WSConstants.WSSE_NS, "Security"));
		HEADERS.add(new QName(WSConstants.WSSE11_NS, "Security"));
		HEADERS.add(new QName(WSConstants.ENC_NS, "EncryptedData"));

		return HEADERS;

	}

	/**
	 * Handles the message, code and decode it depending if it is an outgoing or
	 * incoming one.
	 */

	public boolean handleMessage(SOAPMessageContext messageContext) {

		try {
			// got the message from the context
			SOAPMessage msg = messageContext.getMessage();

			// is outgoing?
			Boolean isOutGoing = (Boolean) messageContext
					.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);

			if (isOutGoing) {

				// if it is outgoing code and sign
				// EncryptUtil.EncryptSOAPEnvelope(msg, prop);
				// EncryptUtil.SignSOAPEnvelope(msg, prop);

			} else {

				// if it is incooming decode and check signature.
				EncryptUtil.checkSignatureAndDecode(msg, this, prop);

			}

		} catch (Exception ex) {

			LOGGER.error("Failed while decoding message", ex);

			throw new RuntimeException(ex.getMessage());
		}
		LOGGER.trace("returning..........");
		return true;

	}

	public boolean handleFault(SOAPMessageContext messageContext) {

		return true;
	}

	/**
	 * Return the jks key, not necesary
	 */

	@SuppressWarnings("deprecation")
	public void handle(Callback[] callbacks) throws IOException,
			UnsupportedCallbackException {

		String password;

		for (Callback cb : callbacks) {

			if (cb instanceof WSPasswordCallback) {

				WSPasswordCallback pc = (WSPasswordCallback) cb;

				try {

					password = prop.getProperty("password");

				} catch (Exception e) {

					throw new UnsupportedCallbackException(pc,
							"failure recovering the key in the properties");

				}

				if (pc.getIdentifer() != null) {
					pc.setPassword(password);
				}

			}

		}

	}

	public void close(MessageContext messageContext) {

	}
}
